'use strict';

require('../../support/test-helper');

var assert = require('../../support/assert');
var TestClient = require('../../support/test-client');

describe('dataviews using tables without overviews', function () {
    var nonOverviewsMapConfig = {
        version: '1.5.0',
        analyses: [
            {
                id: 'data-source',
                type: 'source',
                params: {
                    query: 'select * from populated_places_simple_reduced'
                }
            }
        ],
        dataviews: {
            country_places_count: {
                type: 'formula',
                source: { id: 'data-source' },
                options: {
                    column: 'adm0_a3',
                    operation: 'count'
                }
            },
            country_categories: {
                type: 'aggregation',
                source: { id: 'data-source' },
                options: {
                    column: 'adm0_a3',
                    aggregation: 'count'
                }
            }
        },
        layers: [
            {
                type: 'mapnik',
                options: {
                    sql: 'select * from populated_places_simple_reduced',
                    cartocss: '#layer { marker-fill: red; marker-width: 32; marker-allow-overlap: true; }',
                    cartocss_version: '2.3.0',
                    source: { id: 'data-source' }
                }
            }
        ]
    };

    it('should expose a formula', function (done) {
        var testClient = new TestClient(nonOverviewsMapConfig);
        testClient.getDataview('country_places_count', { own_filter: 0 }, function (err, formulaResult, headers) {
            if (err) {
                return done(err);
            }
            assert.deepStrictEqual(formulaResult, { operation: 'count', result: 7313, nulls: 0, type: 'formula' });

            testClient.drain(done);
        });
    });

    it('should admit a bbox', function (done) {
        var params = {
            bbox: '-170,-80,170,80'
        };
        var testClient = new TestClient(nonOverviewsMapConfig);
        testClient.getDataview('country_places_count', params, function (err, formulaResult) {
            if (err) {
                return done(err);
            }
            assert.deepStrictEqual(formulaResult, { operation: 'count', result: 7253, nulls: 0, type: 'formula' });

            testClient.drain(done);
        });
    });

    describe('filters', function () {
        describe('category', function () {
            it('should expose a filtered formula', function (done) {
                var params = {
                    filters: {
                        dataviews: { country_categories: { accept: ['CAN'] } }
                    }
                };
                var testClient = new TestClient(nonOverviewsMapConfig);
                testClient.getDataview('country_places_count', params, function (err, formulaResult) {
                    if (err) {
                        return done(err);
                    }
                    assert.deepStrictEqual(formulaResult, { operation: 'count', result: 256, nulls: 0, type: 'formula' });
                    testClient.drain(done);
                });
            });

            it('should expose a filtered formula and admit a bbox', function (done) {
                var params = {
                    filters: {
                        dataviews: { country_categories: { accept: ['CAN'] } }
                    },
                    bbox: '-170,-80,170,80'
                };
                var testClient = new TestClient(nonOverviewsMapConfig);
                testClient.getDataview('country_places_count', params, function (err, formulaResult) {
                    if (err) {
                        return done(err);
                    }
                    assert.deepStrictEqual(formulaResult, { operation: 'count', result: 254, nulls: 0, type: 'formula' });
                    testClient.drain(done);
                });
            });
        });
    });
});

describe('dataviews using tables with overviews', function () {
    var overviewsMapConfig = {
        version: '1.5.0',
        analyses: [
            {
                id: 'data-source',
                type: 'source',
                params: {
                    query: 'select * from test_table_overviews'
                }
            },
            {
                id: 'data-source-special-float-values',
                type: 'source',
                params: {
                    query: 'select * from test_special_float_values_table_overviews'
                }
            }
        ],
        dataviews: {
            test_sum: {
                type: 'formula',
                source: { id: 'data-source' },
                options: {
                    column: 'value',
                    operation: 'sum'
                }
            },
            test_categories: {
                type: 'aggregation',
                source: { id: 'data-source' },
                options: {
                    column: 'name',
                    aggregation: 'count',
                    aggregationColumn: 'name'
                }
            },
            test_categories_special_values: {
                type: 'aggregation',
                source: {
                    id: 'data-source-special-float-values'
                },
                options: {
                    column: 'name',
                    aggregation: 'sum',
                    aggregationColumn: 'value'
                }
            },
            test_histogram: {
                type: 'histogram',
                source: { id: 'data-source' },
                options: {
                    column: 'value',
                    bins: 2
                }
            },
            test_histogram_date: {
                type: 'histogram',
                source: { id: 'data-source' },
                options: {
                    column: 'updated_at',
                    bins: 2
                }
            },
            test_histogram_special_values: {
                type: 'histogram',
                source: {
                    id: 'data-source-special-float-values'
                },
                options: {
                    column: 'value',
                    bins: 2
                }
            },
            test_avg: {
                type: 'formula',
                source: { id: 'data-source' },
                options: {
                    column: 'value',
                    operation: 'avg'
                }
            },
            test_formula_sum_special_values: {
                type: 'formula',
                source: {
                    id: 'data-source-special-float-values'
                },
                options: {
                    column: 'value',
                    operation: 'sum'
                }
            },
            test_count: {
                type: 'formula',
                source: { id: 'data-source' },
                options: {
                    column: 'value',
                    operation: 'count'
                }
            },
            test_min: {
                type: 'formula',
                source: { id: 'data-source' },
                options: {
                    column: 'value',
                    operation: 'min'
                }
            },
            test_max: {
                type: 'formula',
                source: { id: 'data-source' },
                options: {
                    column: 'value',
                    operation: 'max'
                }
            }
        },
        layers: [
            {
                type: 'mapnik',
                options: {
                    sql: 'select * from test_table_overviews',
                    cartocss: '#layer { marker-fill: red; marker-width: 32; marker-allow-overlap: true; }',
                    cartocss_version: '2.3.0',
                    source: { id: 'data-source' }
                }
            },
            {
                type: 'mapnik',
                options: {
                    sql: 'select * from test_special_float_values_table_overviews',
                    cartocss: '#layer { marker-fill: red; marker-width: 32; marker-allow-overlap: true; }',
                    cartocss_version: '2.3.0',
                    source: {
                        id: 'data-source-special-float-values'
                    }
                }
            }
        ]
    };

    it('should expose a sum formula', function (done) {
        var testClient = new TestClient(overviewsMapConfig);
        testClient.getDataview('test_sum', { own_filter: 0 }, function (err, formulaResult, headers) {
            if (err) {
                return done(err);
            }
            assert.deepStrictEqual(formulaResult, {
                operation: 'sum',
                result: 15,
                infinities: 0,
                nans: 0,
                nulls: 0,
                type: 'formula'
            });

            testClient.drain(done);
        });
    });

    it('should expose an avg formula', function (done) {
        var testClient = new TestClient(overviewsMapConfig);
        testClient.getDataview('test_avg', { own_filter: 0 }, function (err, formulaResult, headers) {
            if (err) {
                return done(err);
            }
            assert.deepStrictEqual(formulaResult, {
                operation: 'avg',
                result: 3,
                nulls: 0,
                type: 'formula',
                infinities: 0,
                nans: 0
            });

            testClient.drain(done);
        });
    });

    it('should expose a count formula', function (done) {
        var testClient = new TestClient(overviewsMapConfig);
        testClient.getDataview('test_count', { own_filter: 0 }, function (err, formulaResult, headers) {
            if (err) {
                return done(err);
            }
            assert.deepStrictEqual(formulaResult, {
                operation: 'count',
                result: 5,
                nulls: 0,
                type: 'formula',
                infinities: 0,
                nans: 0
            });

            testClient.drain(done);
        });
    });

    it('should expose a max formula', function (done) {
        var testClient = new TestClient(overviewsMapConfig);
        testClient.getDataview('test_max', { own_filter: 0 }, function (err, formulaResult) {
            if (err) {
                return done(err);
            }
            assert.deepStrictEqual(formulaResult, {
                operation: 'max',
                result: 5,
                nulls: 0,
                infinities: 0,
                nans: 0,
                type: 'formula'
            });

            testClient.drain(done);
        });
    });

    it('should expose a min formula', function (done) {
        var testClient = new TestClient(overviewsMapConfig);
        testClient.getDataview('test_min', { own_filter: 0 }, function (err, formulaResult) {
            if (err) {
                return done(err);
            }
            assert.deepStrictEqual(formulaResult, {
                operation: 'min',
                result: 1,
                nulls: 0,
                infinities: 0,
                nans: 0,
                type: 'formula'
            });

            testClient.drain(done);
        });
    });

    it('should admit a bbox', function (done) {
        var params = {
            bbox: '-170,-80,170,80'
        };
        var testClient = new TestClient(overviewsMapConfig);
        testClient.getDataview('test_sum', params, function (err, formulaResult) {
            if (err) {
                return done(err);
            }
            assert.deepStrictEqual(formulaResult, {
                operation: 'sum',
                result: 15,
                nulls: 0,
                infinities: 0,
                nans: 0,
                type: 'formula'
            });

            testClient.drain(done);
        });
    });

    it('should expose a histogram', function (done) {
        var testClient = new TestClient(overviewsMapConfig);
        testClient.getDataview('test_histogram', function (err, histogram, headers) {
            if (err) {
                return done(err);
            }
            assert.ok(histogram);
            assert.strictEqual(histogram.type, 'histogram');
            assert.ok(Array.isArray(histogram.bins));

            testClient.drain(done);
        });
    });

    describe('filters', function () {
        describe('histogram', function () {
            it('should expose a filtered histogram', function (done) {
                var params = {
                    filters: {
                        dataviews: { test_histogram: { min: 2 } }
                    },
                    own_filter: 1
                };
                var testClient = new TestClient(overviewsMapConfig);
                testClient.getDataview('test_histogram', params, function (err, histogram) {
                    if (err) {
                        return done(err);
                    }
                    assert.ok(histogram);
                    assert.strictEqual(histogram.type, 'histogram');
                    assert.ok(Array.isArray(histogram.bins));
                    assert.strictEqual(histogram.bins.length, 4);
                    testClient.drain(done);
                });
            });

            it('should expose a filtered histogram with no results', function (done) {
                var params = {
                    filters: {
                        dataviews: { test_histogram: { max: -1 } }
                    },
                    own_filter: 1
                };
                var testClient = new TestClient(overviewsMapConfig);
                testClient.getDataview('test_histogram', params, function (err, histogram) {
                    if (err) {
                        return done(err);
                    }
                    assert.ok(histogram);
                    assert.strictEqual(histogram.type, 'histogram');
                    assert.ok(Array.isArray(histogram.bins));
                    assert.strictEqual(histogram.bins.length, 0);
                    testClient.drain(done);
                });
            });

            it('should expose a filtered date histogram with no results', function (done) {
                // This most likely works because the overviews will pass
                // the responsibility to the normal dataviews.
                var params = {
                    filters: {
                        dataviews: { test_histogram_date: { max: -1 } }
                    },
                    own_filter: 1
                };
                var testClient = new TestClient(overviewsMapConfig);
                testClient.getDataview('test_histogram_date', params, function (err, histogram) {
                    if (err) {
                        return done(err);
                    }
                    assert.ok(histogram);
                    assert.strictEqual(histogram.type, 'histogram');
                    assert.ok(Array.isArray(histogram.bins));
                    assert.strictEqual(histogram.bins.length, 0);
                    testClient.drain(done);
                });
            });
        });

        describe('category', function () {
            var params = {
                filters: {
                    dataviews: { test_categories: { accept: ['Hawai'] } }
                }
            };

            it('should expose a filtered sum formula', function (done) {
                var testClient = new TestClient(overviewsMapConfig);
                testClient.getDataview('test_sum', params, function (err, formulaResult, headers) {
                    if (err) {
                        return done(err);
                    }
                    assert.deepStrictEqual(formulaResult, {
                        operation: 'sum',
                        result: 1,
                        nulls: 0,
                        infinities: 0,
                        nans: 0,
                        type: 'formula'
                    });

                    testClient.drain(done);
                });
            });

            it('should expose a filtered  avg formula', function (done) {
                var testClient = new TestClient(overviewsMapConfig);
                testClient.getDataview('test_avg', params, function (err, formulaResult, headers) {
                    if (err) {
                        return done(err);
                    }
                    assert.deepStrictEqual(formulaResult, {
                        operation: 'avg',
                        result: 1,
                        nulls: 0,
                        infinities: 0,
                        nans: 0,
                        type: 'formula'
                    });

                    testClient.drain(done);
                });
            });

            it('should expose a filtered count formula', function (done) {
                var testClient = new TestClient(overviewsMapConfig);
                testClient.getDataview('test_count', params, function (err, formulaResult, headers) {
                    if (err) {
                        return done(err);
                    }
                    assert.deepStrictEqual(formulaResult, {
                        operation: 'count',
                        result: 1,
                        infinities: 0,
                        nans: 0,
                        nulls: 0,
                        type: 'formula'
                    });

                    testClient.drain(done);
                });
            });

            it('should expose a filterd max formula', function (done) {
                var testClient = new TestClient(overviewsMapConfig);
                testClient.getDataview('test_max', params, function (err, formulaResult) {
                    if (err) {
                        return done(err);
                    }
                    assert.deepStrictEqual(formulaResult, {
                        operation: 'max',
                        result: 1,
                        nulls: 0,
                        infinities: 0,
                        nans: 0,
                        type: 'formula'
                    });

                    testClient.drain(done);
                });
            });

            it('should expose a filterd min formula', function (done) {
                var testClient = new TestClient(overviewsMapConfig);
                testClient.getDataview('test_min', params, function (err, formulaResult) {
                    if (err) {
                        return done(err);
                    }
                    assert.deepStrictEqual(formulaResult, {
                        operation: 'min',
                        result: 1,
                        nulls: 0,
                        infinities: 0,
                        nans: 0,
                        type: 'formula'
                    });

                    testClient.drain(done);
                });
            });

            it('should expose a filtered sum formula with bbox', function (done) {
                var bboxparams = {
                    filters: {
                        dataviews: { test_categories: { accept: ['Hawai'] } }
                    },
                    bbox: '-170,-80,170,80'
                };
                var testClient = new TestClient(overviewsMapConfig);
                testClient.getDataview('test_sum', bboxparams, function (err, formulaResult) {
                    if (err) {
                        return done(err);
                    }
                    assert.deepStrictEqual(formulaResult, {
                        operation: 'sum',
                        result: 1,
                        nulls: 0,
                        infinities: 0,
                        nans: 0,
                        type: 'formula'
                    });
                    testClient.drain(done);
                });
            });
        });

        describe('aggregation special float values', function () {
            var params = {};

            it('should expose an aggregation dataview filtering special float values out', function (done) {
                var testClient = new TestClient(overviewsMapConfig);
                testClient.getDataview('test_categories_special_values', params, function (err, dataview, headers) {
                    if (err) {
                        return done(err);
                    }

                    assert.deepStrictEqual(dataview, {
                        aggregation: 'sum',
                        count: 5,
                        nulls: 0,
                        nans: 1,
                        infinities: 1,
                        min: 6,
                        max: 6,
                        categoriesCount: 1,
                        categories: [{ category: 'Hawai', value: 6, agg: false }],
                        type: 'aggregation'
                    });

                    testClient.drain(done);
                });
            });

            it('should expose a histogram dataview filtering special float values out', function (done) {
                var testClient = new TestClient(overviewsMapConfig);
                testClient.getDataview('test_histogram_special_values', params, function (err, dataview) {
                    if (err) {
                        return done(err);
                    }
                    assert.deepStrictEqual(dataview, {
                        bin_width: 0,
                        bins_count: 1,
                        bins_start: 3,
                        nulls: 0,
                        infinities: 1,
                        nans: 1,
                        avg: 3,
                        bins: [{ bin: 0, min: 3, max: 3, avg: 3, freq: 2 }],
                        type: 'histogram'
                    });
                    testClient.drain(done);
                });
            });

            it('should expose a formula (sum) dataview filtering special float values out', function (done) {
                var testClient = new TestClient(overviewsMapConfig);
                testClient.getDataview('test_formula_sum_special_values', params, function (err, dataview) {
                    if (err) {
                        return done(err);
                    }
                    assert.deepStrictEqual(dataview, {
                        operation: 'sum',
                        result: 6,
                        nulls: 0,
                        nans: 1,
                        infinities: 1,
                        type: 'formula'
                    });
                    testClient.drain(done);
                });
            });
        });

        describe('agreggation validation', function () {
            const params = {
                response: {
                    status: 400,
                    headers: {
                        'Content-Type': 'application/json; charset=utf-8'
                    }
                }
            };

            function createMapConfig (options) {
                return {
                    version: '1.8.0',
                    analyses: [
                        {
                            id: 'data-source',
                            type: 'source',
                            params: {
                                query: 'select * from test_table_overviews'
                            }
                        }
                    ],
                    dataviews: {
                        test_invalid_aggregation: {
                            type: 'aggregation',
                            source: { id: 'data-source' },
                            options: options
                        }
                    },
                    layers: [
                        {
                            type: 'mapnik',
                            options: {
                                sql: 'select * from test_table_overviews',
                                cartocss: '#layer { marker-fill: red; marker-width: 32; marker-allow-overlap: true; }',
                                cartocss_version: '2.3.0',
                                source: { id: 'data-source' }
                            }
                        }
                    ]
                };
            }

            it('should fail if missing column', function (done) {
                var options = {
                    aggregation: 'sum',
                    aggregationColumn: 'value'
                };
                var missingColumnMapConfig = createMapConfig(options);

                var testClient = new TestClient(missingColumnMapConfig);
                testClient.getDataview('test_invalid_aggregation', params, function (err, dataview) {
                    if (err) {
                        return done(err);
                    }

                    assert.deepStrictEqual(dataview, {
                        errors: ["Aggregation expects 'column' in dataview options"],
                        errors_with_context: [{
                            type: 'unknown',
                            message: "Aggregation expects 'column' in dataview options"
                        }]
                    });

                    testClient.drain(done);
                });
            });

            it('should fail if no aggregation operation', function (done) {
                var options = {
                    column: 'value',
                    aggregationColumn: 'value'
                };
                var missingOperationMapConfig = createMapConfig(options);

                var testClient = new TestClient(missingOperationMapConfig);
                testClient.getDataview('test_invalid_aggregation', params, function (err, dataview) {
                    if (err) {
                        return done(err);
                    }

                    assert.deepStrictEqual(dataview, {
                        errors: ["Aggregation expects 'aggregation' operation in dataview options"],
                        errors_with_context: [{
                            type: 'unknown',
                            message: "Aggregation expects 'aggregation' operation in dataview options"
                        }]
                    });

                    testClient.drain(done);
                });
            });

            it('should fail if fake operation', function (done) {
                var options = {
                    column: 'value',
                    aggregation: 'wadus',
                    aggregationColumn: 'value'
                };
                var wrongOperationMapConfig = createMapConfig(options);

                var testClient = new TestClient(wrongOperationMapConfig);
                testClient.getDataview('test_invalid_aggregation', params, function (err, dataview) {
                    if (err) {
                        return done(err);
                    }

                    assert.deepStrictEqual(dataview, {
                        errors: ["Aggregation does not support 'wadus' operation"],
                        errors_with_context: [{
                            type: 'unknown',
                            message: "Aggregation does not support 'wadus' operation"
                        }]
                    });

                    testClient.drain(done);
                });
            });

            it('should fail if invalid operation for overview', function (done) {
                var options = {
                    column: 'value',
                    aggregation: 'avg',
                    aggregationColumn: 'value'
                };
                var wrongOperationMapConfig = createMapConfig(options);

                var testClient = new TestClient(wrongOperationMapConfig);
                testClient.getDataview('test_invalid_aggregation', params, function (err, dataview) {
                    if (err) {
                        return done(err);
                    }

                    assert.deepStrictEqual(dataview, {
                        errors: ["Aggregation does not support 'avg' operation in dataview overview options"],
                        errors_with_context: [{
                            type: 'unknown',
                            message: "Aggregation does not support 'avg' operation in dataview overview options"
                        }]
                    });

                    testClient.drain(done);
                });
            });

            it('should fail if no aggregation column when needed', function (done) {
                var options = {
                    column: 'value',
                    aggregation: 'sum'
                };
                var missingOptionMapConfig = createMapConfig(options);

                var testClient = new TestClient(missingOptionMapConfig);
                testClient.getDataview('test_invalid_aggregation', params, function (err, dataview) {
                    if (err) {
                        return done(err);
                    }

                    assert.deepStrictEqual(dataview, {
                        errors: ["Aggregation 'sum' is missing some options: aggregationColumn"],
                        errors_with_context: [{
                            type: 'unknown',
                            message: "Aggregation 'sum' is missing some options: aggregationColumn"
                        }]
                    });

                    testClient.drain(done);
                });
            });
        });
    });
});
